package com.smedi.ismedi.inmail.core.application.service.impl;

import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.smedi.ismedi.authority.starter.util.VerifyHelper;
import com.smedi.ismedi.inmail.api.constant.ConstantField;
import com.smedi.ismedi.inmail.core.application.service.LoginLogUserStaticsApplicationService;
import com.smedi.ismedi.inmail.core.domain.aggregatemodel.LoginLogUserStatics;
import com.smedi.ismedi.inmail.core.port.adapter.persistence.model.LoginLogEntity;
import com.smedi.ismedi.inmail.core.port.adapter.persistence.model.LoginLogUserStaticsEntity;
import com.smedi.ismedi.inmail.core.port.adapter.persistence.model.OperationLogEntity;
import com.smedi.ismedi.inmail.core.port.adapter.persistence.mybatis.mapper.LoginLogMapper;
import com.smedi.ismedi.inmail.core.port.adapter.persistence.mybatis.mapper.LoginLogUserStaticsMapper;
import com.smedi.ismedi.inmail.core.port.adapter.persistence.mybatis.mapper.OperationLogMapper;
import com.smedi.ismedi.inmail.core.port.adapter.service.FileRemoteApi;
import com.smedi.ismedi.organization.api.dto.request.OrganizationListQueryRequest;
import com.smedi.ismedi.organization.api.dto.response.resp.OrganizationResp;
import com.smedi.ismedi.organization.api.feign.RemoteHrOrganizationFeignApi;
import com.smedi.ismedi.personnel.api.dto.request.PersonListQueryRequest;
import com.smedi.ismedi.personnel.api.dto.response.resp.PersonDetailResp;
import com.smedi.ismedi.personnel.api.feign.RemotePersonCoreFeignApi;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;

/**
 * 通知公告应用服务实现类
 *
 * @author jiangqinxuan
 * @date 2021/07/26
 */
@Service
@RequiredArgsConstructor
@Transactional
@Slf4j
public class LoginLogUserStaticsApplicationServiceImpl extends ServiceImpl<LoginLogUserStaticsMapper, LoginLogUserStaticsEntity>
        implements LoginLogUserStaticsApplicationService {

    private final LoginLogMapper loginLogMapper;
    private final FileRemoteApi fileRemoteApi;
    private final OperationLogMapper operationLogMapper;
    private final RemotePersonCoreFeignApi remotePersonCoreFeignApi;
    private final RemoteHrOrganizationFeignApi remoteHrOrganizationFeignApi;

    @Override
    public void statics() {
        List<Long> removeIds = new ArrayList<>();
        List<LoginLogUserStaticsEntity> staticsCreateds = new ArrayList<>();
        List<LoginLogUserStaticsEntity> staticsUpdateds = new ArrayList<>();

        List<PersonDetailResp> people = remotePersonCoreFeignApi.getPersonDetailList(PersonListQueryRequest.builder().isDelete(false).build()).getData();
        Map<Long, PersonDetailResp> idPersonMap = new HashMap<>();
        for (PersonDetailResp person : people) {
            idPersonMap.put(person.getId(), person);
        }
        List<OrganizationResp> organizations = remoteHrOrganizationFeignApi.getOrganizationList(OrganizationListQueryRequest.builder().build()).getData();
        Map<Long, OrganizationResp> idOrganizationMap = new HashMap<>();
        for (OrganizationResp organization : organizations) {
            idOrganizationMap.put(organization.getId(), organization);
        }
        List<LoginLogEntity> loginLogs = loginLogMapper.selectList(new LambdaQueryWrapper<LoginLogEntity>()
                .ge(LoginLogEntity::getLoginTime, LocalDateTime.of(LocalDate.now().minusDays(1), LocalTime.MIDNIGHT))
                .lt(LoginLogEntity::getLoginTime, LocalDateTime.of(LocalDate.now(), LocalTime.MIDNIGHT)));
        List<LoginLogEntity> loginLog30Day = loginLogMapper.selectList(new LambdaQueryWrapper<LoginLogEntity>()
                .ge(LoginLogEntity::getLoginTime, LocalDateTime.of(LocalDate.now().minusDays(30), LocalTime.MIDNIGHT))
                .lt(LoginLogEntity::getLoginTime, LocalDateTime.of(LocalDate.now(), LocalTime.MIDNIGHT)));
        List<LoginLogEntity> loginLog90Day = loginLogMapper.selectList(new LambdaQueryWrapper<LoginLogEntity>()
                .ge(LoginLogEntity::getLoginTime, LocalDateTime.of(LocalDate.now().minusDays(90), LocalTime.MIDNIGHT))
                .lt(LoginLogEntity::getLoginTime, LocalDateTime.of(LocalDate.now(), LocalTime.MIDNIGHT)));
        Map<Long, Long> personIdMinutePc = new HashMap<>(1024);
        Map<Long, Long> personIdMinuteMobile = new HashMap<>(1024);
        Map<Long, Integer> personIdLoginNumber = new HashMap<>(1024);
        Map<Long, LoginLogEntity> personIdLatestLoginLogMap = new HashMap<>(1024);
        Map<Long, List<Long>> personIdLoginLogIdsMap = new HashMap<>(1024);
        Set<Long> loginLogIds = new HashSet<>();
        for (LoginLogEntity loginLog : loginLogs) {
            Long personId = loginLog.getLoginUserId();
            LoginLogEntity latestLoginLog = personIdLatestLoginLogMap.get(personId);
            if (null == latestLoginLog || loginLog.getLoginTime().isAfter(latestLoginLog.getLoginTime())) {
                personIdLatestLoginLogMap.put(personId, loginLog);
            }
            Integer loginNumber = personIdLoginNumber.getOrDefault(personId, 0);
            personIdLoginNumber.put(personId, loginNumber + 1);
            if (loginLog.getClientType().equals(ConstantField.ClientTypeEnum.PC.name())) {
                Long minutePc = personIdMinutePc.getOrDefault(personId, 0L);
                personIdMinutePc.put(personId, minutePc + loginLog.getDuration());
            }
            if (loginLog.getClientType().equals(ConstantField.ClientTypeEnum.MOBILE.name())) {
                Long minuteMobile = personIdMinuteMobile.getOrDefault(personId, 0L);
                personIdMinuteMobile.put(personId, minuteMobile + loginLog.getDuration());
            }
            List<Long> loginLogIdsByPersonId = personIdLoginLogIdsMap.getOrDefault(personId, new ArrayList<>());
            loginLogIdsByPersonId.add(loginLog.getId());
            personIdLoginLogIdsMap.put(personId, loginLogIdsByPersonId);
            loginLogIds.add(loginLog.getId());
        }

        Map<Long, Long> personIdMinute30DayMap = new HashMap<>(1024);
        Map<Long, Long> personIdPCMinute30DayMap = new HashMap<>(1024);
        Map<Long, Long> personIdMobileMinute30DayMap = new HashMap<>(1024);
        for (LoginLogEntity loginLog : loginLog30Day) {
            Long personId = loginLog.getLoginUserId();
            personIdMinute30DayMap.put(personId, personIdMinute30DayMap.getOrDefault(personId, 0L) + loginLog.getDuration());
            // 统计30天内登录时长
            if (loginLog.getClientType().equals(ConstantField.ClientTypeEnum.PC.name())) {
                Long minutePc = personIdPCMinute30DayMap.getOrDefault(personId, 0L);
                personIdPCMinute30DayMap.put(personId, minutePc + loginLog.getDuration());
            }
            if (loginLog.getClientType().equals(ConstantField.ClientTypeEnum.MOBILE.name())) {
                Long minuteMobile = personIdMobileMinute30DayMap.getOrDefault(personId, 0L);
                personIdMobileMinute30DayMap.put(personId, minuteMobile + loginLog.getDuration());
            }
        }

        // 90天Log统计
        Map<Long, Long> personIdMinute90DayMap = new HashMap<>(1024);
        Map<Long, Integer> personIdLoginNumber90DayMap = new HashMap<>(1024);
        Map<Long, Long> personIdMinutePc90DayMap = new HashMap<>(1024);
        Map<Long, Long> personIdMinuteMobile90DayMap = new HashMap<>(1024);
        for (LoginLogEntity loginLog : loginLog90Day) {
            Long personId = loginLog.getLoginUserId();
            personIdMinute90DayMap.put(personId,
                    personIdMinute90DayMap.getOrDefault(personId, 0L) + loginLog.getDuration());
            // 统计90天内登录次数
            Integer loginNumber = personIdLoginNumber90DayMap.getOrDefault(personId, 0);
            personIdLoginNumber90DayMap.put(personId, loginNumber + 1);
            // 统计90天内登录时长
            if (loginLog.getClientType().equals(ConstantField.ClientTypeEnum.PC.name())) {
                Long minutePc = personIdMinutePc90DayMap.getOrDefault(personId, 0L);
                personIdMinutePc90DayMap.put(personId, minutePc + loginLog.getDuration());
            }
            if (loginLog.getClientType().equals(ConstantField.ClientTypeEnum.MOBILE.name())) {
                Long minuteMobile = personIdMinuteMobile90DayMap.getOrDefault(personId, 0L);
                personIdMinuteMobile90DayMap.put(personId, minuteMobile + loginLog.getDuration());
            }
        }
        List<OperationLogEntity> operationLogEntities = new ArrayList<>();
        if (VerifyHelper.isAvailable(loginLogIds)) {
            operationLogEntities = operationLogMapper.selectList(new LambdaQueryWrapper<OperationLogEntity>().in(OperationLogEntity::getLoginLogId, loginLogIds));
        }
        Map<Long, List<OperationLogEntity>> loginLogIdOperationLogs = new HashMap<>(1024);
        for (OperationLogEntity operationLog : operationLogEntities) {
            List<OperationLogEntity> operationLogsByLoginLogId = loginLogIdOperationLogs.getOrDefault(operationLog.getLoginLogId(), new ArrayList<>());
            operationLogsByLoginLogId.add(operationLog);
            loginLogIdOperationLogs.put(operationLog.getLoginLogId(), operationLogsByLoginLogId);
        }

        List<LoginLogUserStaticsEntity> loginLogUserStatics = this.getBaseMapper().selectList(null);
        Map<Long, LoginLogUserStaticsEntity> personIdStaticsMap = new HashMap<>();
        for (LoginLogUserStaticsEntity loginLogUserStaticsTemp : loginLogUserStatics) {
            personIdStaticsMap.put(loginLogUserStaticsTemp.getPersonId(), loginLogUserStaticsTemp);
            PersonDetailResp person = idPersonMap.get(loginLogUserStaticsTemp.getPersonId());
            if (null == person) {
                removeIds.add(loginLogUserStaticsTemp.getId());
            }
        }

        for (PersonDetailResp person : people) {
            Long personId = person.getId();
            LoginLogUserStaticsEntity statics = personIdStaticsMap.get(personId);
            OperationLogEntity operationLog = null;
            List<Long> loginLogIdsByPersonId = personIdLoginLogIdsMap.getOrDefault(personId, new ArrayList<>());
            for (Long loginLogId : loginLogIdsByPersonId) {
                List<OperationLogEntity> operationLogs = loginLogIdOperationLogs.getOrDefault(loginLogId, new ArrayList<>());
                for (OperationLogEntity operationLogCurrent : operationLogs) {
                    if (null == operationLog || operationLogCurrent.getCreatedDate().isAfter(operationLog.getCreatedDate())) {
                        operationLog = operationLogCurrent;
                    }
                }
            }
            if (null == statics) {
                staticsCreateds.add(LoginLogUserStaticsEntity.builder()
                        .personId(personId)
                        .personName(person.getLastName())
                        .employeeNum(person.getEmployeeNum())
                        .latestIp(null == personIdLatestLoginLogMap.get(personId) ? null : personIdLatestLoginLogMap.get(personId).getIp())
                        .latestLoginTime(null == personIdLatestLoginLogMap.get(personId) ? null : personIdLatestLoginLogMap.get(personId).getLoginTime())
                        .latestOperationModule(null == operationLog ? null : operationLog.getModule())
                        .loginNumber(personIdLoginNumber.getOrDefault(personId, 0))
                        .minuteMobile(personIdMinuteMobile.getOrDefault(personId, 0L))
                        .minutePc(personIdMinutePc.getOrDefault(personId, 0L))
                        .minute30Day(personIdMinute30DayMap.getOrDefault(personId, 0L))
                        .totalMinute(personIdMinute90DayMap.getOrDefault(personId, 0L))
                        .pcMinute30Day(personIdPCMinute30DayMap.getOrDefault(personId, 0L))
                        .mobileMinute30Day(personIdMobileMinute30DayMap.getOrDefault(personId, 0L))
                        .organizationId(null == person.getOrganizationAssignment() ? null : person.getOrganizationAssignment().getHrOrganizationId())
                        .organizationName(null == person.getOrganizationAssignment() ? null : idOrganizationMap
                                .get(person.getOrganizationAssignment().getHrOrganizationId())
                                .getShortName())
                        .build());
                continue;
            }
            LoginLogUserStaticsEntity staticsUpdated = LoginLogUserStaticsEntity.builder()
                    .personName(person.getLastName())
                    .employeeNum(person.getEmployeeNum())
                    .latestIp(null == personIdLatestLoginLogMap.get(personId) ? null : personIdLatestLoginLogMap.get(personId).getIp())
                    .latestLoginTime(null == personIdLatestLoginLogMap.get(personId) ? null : personIdLatestLoginLogMap.get(personId).getLoginTime())
                    .latestOperationModule(null == operationLog ? null : operationLog.getModule())
                    .loginNumber(personIdLoginNumber90DayMap.getOrDefault(personId, 0))
                    .minuteMobile(personIdMinuteMobile90DayMap.getOrDefault(personId, 0L))
                    .minutePc(personIdMinutePc90DayMap.getOrDefault(personId, 0L))
                    .minute30Day(personIdMinute30DayMap.getOrDefault(personId, 0L))
                    .totalMinute(personIdMinute90DayMap.getOrDefault(personId, 0L))
                    .pcMinute30Day(personIdPCMinute30DayMap.getOrDefault(personId, 0L))
                    .mobileMinute30Day(personIdMobileMinute30DayMap.getOrDefault(personId, 0L))
                    .organizationId(null == person.getOrganizationAssignment() ? null : person.getOrganizationAssignment().getHrOrganizationId())
                    .organizationName(null == person.getOrganizationAssignment() ? null : idOrganizationMap
                            .get(person.getOrganizationAssignment().getHrOrganizationId())
                            .getShortName())
                    .build();
            staticsUpdated.setId(statics.getId());
            staticsUpdateds.add(staticsUpdated);
        }

        if (VerifyHelper.isAvailable(removeIds)) {
            this.getBaseMapper().deleteBatchIds(removeIds);
        }
        saveBatch(staticsCreateds);
        updateBatchById(staticsUpdateds);
    }

    @Override
    @SneakyThrows
    public String export(List<LoginLogUserStatics> loginLogUserStatics) {
        String fileKey = "";
        ClassPathResource classPathResource = new ClassPathResource("fileTemplate/LoginLogUserStatics.xlsx");
        XSSFWorkbook workBook;
        try (InputStream inputStream = classPathResource.getInputStream()) {
            workBook = new XSSFWorkbook(inputStream);
        }
        XSSFSheet buildingSheet = workBook.cloneSheet(0, "登录统计");
        XSSFCellStyle cellStyle = workBook.createCellStyle();
        // 启动单元格内换行
        cellStyle.setWrapText(true);
        cellStyle.setBorderBottom(BorderStyle.THIN);
        cellStyle.setBorderTop(BorderStyle.THIN);
        cellStyle.setBorderRight(BorderStyle.THIN);
        cellStyle.setBorderLeft(BorderStyle.THIN);
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        cellStyle.setAlignment(HorizontalAlignment.CENTER);
        int i = 0;
        for (LoginLogUserStatics loginLogUserStatic : loginLogUserStatics) {
            i++;
            XSSFRow rowAdd = buildingSheet.createRow(i);
            rowAdd.setHeight((short) 600);
            for (int j = 0; j < 12; j++) {
                rowAdd.createCell(j);
                rowAdd.getCell(j).setCellStyle(cellStyle);
            }
            rowAdd.getCell(0).setCellValue(i);
            rowAdd.getCell(1).setCellValue(loginLogUserStatic.getPersonName());
            rowAdd.getCell(2).setCellValue(loginLogUserStatic.getEmployeeNum());
            rowAdd.getCell(3).setCellValue(loginLogUserStatic.getOrganizationName());
            rowAdd.getCell(4).setCellValue(loginLogUserStatic.getLatestIp());
            rowAdd.getCell(5).setCellValue(loginLogUserStatic.getLatestLoginTime());
            rowAdd.getCell(6).setCellValue(loginLogUserStatic.getLatestOperationModule());
            rowAdd.getCell(7).setCellValue(loginLogUserStatic.getLoginNumber());
            rowAdd.getCell(8).setCellValue(loginLogUserStatic.getMinutePc());
            rowAdd.getCell(9).setCellValue(loginLogUserStatic.getMinuteMobile());
            rowAdd.getCell(10).setCellValue(loginLogUserStatic.getMinute30Day());
            rowAdd.getCell(11).setCellValue(loginLogUserStatic.getTotalMinute());
        }

        workBook.removeSheetAt(0);

        final File outputFile = File.createTempFile(IdUtil.simpleUUID(), ".xlsx");
        try (FileOutputStream outputStream = new FileOutputStream(outputFile)) {
            workBook.write(outputStream);
            fileKey = fileRemoteApi.uploadFile(outputFile);
        } catch (Exception e) {
            log.info(e.getMessage());
        } finally {
            Files.deleteIfExists(Paths.get(outputFile.getAbsolutePath()));
        }

        return fileKey;
    }
}
